import { anyOf, charIn, charNotIn, createRegExp, digit, exactly, letter, oneOrMore } from "magic-regexp";
import ipaddr from "ipaddr.js";
import { IpAddress, IpRange } from "cidr-calc";
import * as path$2 from "path";
import path from "path";
import fs, { existsSync } from "node:fs";
import consola, { consola as consola$1 } from "consola";
import child from "node:child_process";
import util from "node:util";
import * as fs$1 from "fs";
import { fileURLToPath } from "url";
import { readFile, writeFile } from "node:fs/promises";
import path$1 from "node:path";
import { open } from "lmdb";
import { finished } from "node:stream/promises";
import { Readable } from "node:stream";
import { Readable as Readable$1 } from "stream";
import { chain } from "stream-chain";
import { parser } from "stream-json";
import { streamArray } from "stream-json/streamers/stream-array.js";
import os from "node:os";
import JsonToTS from "@riavzon/json-to-ts";

//#region src/scripts/goodBotsScrapper/regex.ts
const octet = anyOf(exactly("25", charIn("012345")), exactly("2", charIn("01234"), digit), exactly(charIn("01"), digit, digit), exactly(digit, digit), digit);
const ipv4Base = anyOf(exactly(octet, ".", octet, ".", octet, ".", octet));
const hexWord = anyOf(digit, charIn("abcdefABCDEF")).times.between(1, 4);
const ipv6Base = anyOf(exactly(hexWord, ":").times(7).and(hexWord), exactly(exactly(hexWord, ":").times.between(3, 7), ":"), exactly(hexWord, ":").times.between(1, 5).and("::").and(exactly(hexWord, ":").times.any()).and(hexWord), exactly("::").and(anyOf(hexWord, ipv4Base, exactly(hexWord, ":").times.any().and(hexWord))), exactly(hexWord, ":").times.between(1, 7).and(":"));
/**
* Base regex pattern matching a single IPv4 or IPv6 address.
* Used as a building block by {@link botIpExtractor}.
*/
const ipBase = anyOf(ipv4Base, ipv6Base).notAfter(anyOf(letter, digit, ":"));
/**
* Global regex that extracts IP addresses, CIDR ranges, and IP range pairs
* from raw text. Named capture groups: `ip1`, optional `ip2` (range end),
* and optional `mask` (CIDR prefix length).
*/
const botIpExtractor = createRegExp(ipBase.groupedAs("ip1"), anyOf(exactly("/", digit.times.between(1, 3).groupedAs("mask")), exactly(anyOf(" to ", " - ", "-", " and "), ipBase.groupedAs("ip2"))).optionally(), ["g", "i"]);

//#endregion
//#region src/scripts/goodBotsScrapper/ipNormalizers.ts
/**
* Parses and normalizes an IP address or CIDR string into canonical form.
*
* @param ip - A raw IPv4/IPv6 address or CIDR string (for example, `"1.1.1.0/24"`).
* @returns The normalized IP or CIDR string.
* @throws If the input is empty, not a valid IP, or not a valid CIDR.
*/
const normalizeIp = (ip) => {
	if (!ip) throw new Error("Empty IP string");
	if (ip.includes("/")) {
		const [baseIp, mask] = ip.split("/");
		if (!ipaddr.isValidCIDR(ip)) throw new Error("Invalid CIDR");
		return `${ipaddr.parse(baseIp).toString()}/${mask}`;
	}
	if (!ipaddr.isValid(ip)) throw new Error("Invalid Ip address");
	return ipaddr.parse(ip).toString();
};
/**
* Converts a regex match from {@link botIpExtractor} into one or more
* canonical CIDR strings. Handles plain IPs, CIDR notation, and IP ranges
* (expanded to CIDRs via `cidr-calc`).
*
* @param ip1 - The first IP address from the match.
* @param ip2 - Optional second IP for range notation.
* @param mask - Optional CIDR prefix length.
* @returns A single CIDR string, or an array of CIDR strings for IP ranges.
* @throws If any IP or CIDR is invalid.
*/
const normalizeExtractedMatch = (ip1, ip2, mask) => {
	const cleanIp1 = normalizeIp(ip1);
	if (ip2 && !mask) {
		const cleanIp2 = normalizeIp(ip2);
		return new IpRange(IpAddress.of(cleanIp1), IpAddress.of(cleanIp2)).toCidrs().map((c) => c.toString());
	}
	if (mask) {
		const cidr = `${cleanIp1}/${mask}`;
		if (!ipaddr.isValidCIDR(cidr)) throw new Error("Invalid Extracted CIDR");
		return cidr;
	}
	return cleanIp1;
};

//#endregion
//#region src/scripts/goodBotsScrapper/urls.ts
const urls = [
	{
		name: "google",
		type: "JSON",
		urls: [
			"https://developers.google.com/static/search/apis/ipranges/googlebot.json",
			"https://developers.google.com/static/search/apis/ipranges/special-crawlers.json",
			"https://developers.google.com/static/search/apis/ipranges/user-triggered-fetchers.json",
			"https://developers.google.com/static/search/apis/ipranges/user-triggered-fetchers-google.json",
			"https://www.gstatic.com/ipranges/goog.json"
		]
	},
	{
		name: "bing",
		type: "JSON",
		urls: ["https://www.bing.com/toolbox/bingbot.json"]
	},
	{
		name: "openai",
		type: "JSON",
		urls: ["https://openai.com/gptbot.json", "https://openai.com/searchbot.json"]
	},
	{
		name: "apple",
		type: "JSON",
		urls: ["https://search.developer.apple.com/applebot.json"]
	},
	{
		name: "ahrefs",
		type: "JSON",
		urls: ["https://api.ahrefs.com/v3/public/crawler-ip-ranges"]
	},
	{
		name: "duckDuckGo",
		type: "HTML",
		urls: ["https://duckduckgo.com/duckduckgo-help-pages/results/duckassistbot", "https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot"]
	},
	{
		name: "commonCrawler",
		type: "HTML",
		urls: ["https://commoncrawl.org/faq"]
	},
	{
		name: "xAndTwitter",
		type: "HTML",
		urls: ["https://developer.x.com/en/docs/x-for-websites/cards/guides/troubleshooting-cards"]
	},
	{
		name: "facebook",
		type: "CSV",
		urls: ["https://www.facebook.com/peering/geofeed"]
	},
	{
		name: "pinterest",
		type: "HTML",
		urls: ["https://help.pinterest.com/en/business/article/pinterestbot"]
	},
	{
		name: "telegram",
		type: "HTML",
		urls: ["https://core.telegram.org/bots/webhooks"]
	},
	{
		name: "semrush",
		type: "HTML",
		urls: ["https://www.semrush.com/kb/1149-issues-with-crawling-a-domain"]
	}
];

//#endregion
//#region src/utils/run.ts
const execAsync = util.promisify(child.exec);
/**
* Executes a shell command asynchronously and returns its stdout and stderr.
*
* @param command - The shell command to execute.
* @param options - Optional execution settings. Set `silent: true` to suppress
*   console output.
* @returns A promise resolving to `{ stdout, stderr }`.
* @throws If the command exits with a non-zero status.
*/
const run = async (command, options = {}) => {
	try {
		const { stdout, stderr } = await execAsync(command, {
			...options,
			maxBuffer: options.maxBuffer ?? 1024 * 1024 * 10
		});
		const result = {
			stdout: typeof stdout === "string" ? stdout.trim() : stdout,
			stderr: typeof stderr === "string" ? stderr.trim() : stderr
		};
		if (!options.silent) {
			console.log(`[run]: ${command}`);
			if (result.stdout) console.log("stdout:", result.stdout);
			if (result.stderr) console.error("stderr:", result.stderr);
		}
		return result;
	} catch (error) {
		console.error(`Execution failed for command: "${command}"`);
		if (typeof error === "object" && error !== null) {
			const e = error;
			if (e.stderr) console.error("Error Output:", e.stderr);
		}
		throw error;
	}
};

//#endregion
//#region src/scripts/goodBotsScrapper/scrappers/jsonScrapper.ts
const logger$17 = consola.withTag("GOOD-BOT-CRAWLER");
async function jsonScrapper(name, urls) {
	logger$17.start(`Processing provider: ${name}...`);
	const promises = urls.map(async (url) => {
		const res = await fetch(url);
		if (!res.ok) throw new Error(`fetch ${name} ${url} -> ${String(res.status)}`);
		const data = await res.json();
		return Array.isArray(data) ? data : [data];
	});
	return (await Promise.all(promises)).flat();
}

//#endregion
//#region src/scripts/goodBotsScrapper/scrappers/fetchers.ts
const logger$16 = consola.withTag("GOOD-BOT-CRAWLER");
let isCurlVerified = false;
async function fetchWithCurl(url) {
	if (!isCurlVerified) try {
		await run("command -v curl", { silent: true });
		isCurlVerified = true;
	} catch {
		logger$16.error("curl needed to be installed to run goodBots scrapper in order to avoid being blocked.");
		throw new Error("Fatal: curl is required for fallback scraping.");
	}
	try {
		const { stdout } = await run(`curl -sL ${url}`, { silent: true });
		return typeof stdout === "string" ? stdout : stdout.toString("utf-8");
	} catch (err) {
		logger$16.warn(`curl failed for ${url}: ${String(err)}`);
		return "";
	}
}
async function downloadHtml(url) {
	try {
		const response = await fetch(url, { headers: {
			"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
			"Accept": "text/html,text/csv,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
		} });
		if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.statusText} - ${String(response.status)}`);
		return await response.text();
	} catch (err) {
		logger$16.warn(`Fetch failed for ${url}:\n ${String(err)},\n Using curl instead...`);
		return await fetchWithCurl(url);
	}
}

//#endregion
//#region src/scripts/goodBotsScrapper/scrappers/htmlScrapper.ts
const logger$15 = consola.withTag("GOOD-BOT-CRAWLER");
async function htmlScrapper(urls, name) {
	logger$15.start(`Processing provider: ${name}...`);
	const allMatches = (await Promise.all(urls.map(downloadHtml))).flatMap((txt) => {
		return Array.from(txt.matchAll(botIpExtractor)).flatMap((m) => {
			if (!m.groups?.ip1) return [];
			try {
				const normalized = normalizeExtractedMatch(m.groups.ip1, m.groups.ip2, m.groups.mask);
				return Array.isArray(normalized) ? normalized : [normalized];
			} catch (err) {
				logger$15.warn(`Failed to normalize extracted IP match from ${name}:`, m[0], `Message: ${String(err)}`);
				return [];
			}
		});
	});
	return [{
		creationTime: (/* @__PURE__ */ new Date()).toISOString(),
		prefixes: Array.from(new Set(allMatches)).map((ip) => ({ ipv4Prefix: ip }))
	}];
}

//#endregion
//#region src/scripts/goodBotsScrapper/scrapper.ts
const logger$14 = consola.withTag("GOOD-BOT-CRAWLER");
/**
* Scrapes IP ranges from official provider geofeeds (Google, Bing, Apple,
* Meta, and others) and compiles them into an MMDB database. Accepts optional
* custom provider configs to extend the built-in dataset.
* Output: `<outputPath>/goodBots.mmdb`.
*
* @param outputPath - Directory where the compiled `goodBots.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
* @param customUrls - Optional list of additional provider URL configs to merge.
*   Each entry must include `name`, `type` (`"JSON"` | `"CSV"` | `"HTML"`), and `urls`.
*/
async function getCrawlersIps(outputPath, mmdbPath, customUrls) {
	logger$14.start("Starting Good Bots IP scraper...");
	const databasePath = path.resolve(outputPath, "./goodBots.mmdb");
	const providers = [...urls, ...customUrls ?? []];
	if (customUrls && customUrls.length > 0) customUrls.forEach((prov) => {
		if (!prov.name || !prov.type || !prov.urls) throw new Error(`Provider ${prov.name || "unknown"} is missing required fields.`);
	});
	const ipDb = {};
	for (const provider of providers) if (provider.type === "JSON") ipDb[provider.name] = await jsonScrapper(provider.name, provider.urls);
	else ipDb[provider.name] = await htmlScrapper(provider.urls, provider.name);
	consola.info(`Building data...`);
	const mmdbRecords = Object.entries(ipDb).flatMap(([provider, snapshots]) => {
		return snapshots.flatMap((snap) => {
			if (typeof snap === "string") try {
				return [{
					range: normalizeIp(snap),
					provider,
					syncToken: "unknown",
					creationTime: (/* @__PURE__ */ new Date()).toISOString()
				}];
			} catch (err) {
				logger$14.warn(`Invalid ip format skipping: ${snap} -> ${String(err)}`);
				return null;
			}
			return snap.prefixes.map((p) => {
				const rawIp = p.ipv4Prefix ?? p.ipv6Prefix ?? "";
				if (!rawIp) return null;
				try {
					return {
						range: normalizeIp(rawIp),
						provider,
						syncToken: snap.syncToken,
						creationTime: snap.creationTime
					};
				} catch (err) {
					logger$14.warn(`Invalid ip format skipping from prefix: ${rawIp} -> ${String(err)}`);
					return null;
				}
			}).filter(Boolean);
		});
	});
	logger$14.info(`Total records to import: ${String(mmdbRecords.length)}`);
	const tempJsonPath = path.resolve(outputPath, "temp_bots.json");
	const jsonLines = mmdbRecords.map((r) => JSON.stringify(r)).join("\n");
	fs.writeFileSync(tempJsonPath, jsonLines, "utf-8");
	try {
		logger$14.info("Compiling MMDB...");
		await run(`${mmdbPath} import --in ${tempJsonPath} --out ${databasePath}`);
		logger$14.success(`Successfully generated ${databasePath}`);
	} catch (err) {
		logger$14.error(`Failed to compile MMDB: ${String(err)}`);
		throw err;
	} finally {
		if (fs.existsSync(tempJsonPath)) fs.unlinkSync(tempJsonPath);
	}
}

//#endregion
//#region src/scripts/bgp.ts
const logger$13 = consola$1.withTag("[ASN/BGP]");
/**
* Fetches ASN definitions and the BGP routing table from bgp.tools, merges
* them into enriched records, and compiles the result into an MMDB database.
* Output: `<outputPath>/asn.mmdb`.
*
* @param userAgent - Contact string required by bgp.tools (format: `<name> [url] - <email>`).
* @param outputPath - Directory where the compiled `asn.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function getBGPAndASN(userAgent, outputPath, mmdbPath) {
	const urls = ["https://bgp.tools/asns.csv", "https://bgp.tools/table.jsonl"];
	const output = path.resolve(outputPath, "asn.mmdb");
	const tempASNJson = path.resolve(outputPath, "temp_asn_data.json");
	try {
		logger$13.info("\nFetching ASN Dictionary from BGP.tools...");
		const rawAsnCsv = await (await fetch(urls[0], {
			method: "GET",
			headers: { "User-Agent": userAgent }
		})).text();
		const asnDictionary = /* @__PURE__ */ new Map();
		const asnLines = rawAsnCsv.split("\n").slice(1);
		const regex = createRegExp(exactly("AS").and(oneOrMore(digit).groupedAs("asnNumber")));
		const nonQuotes = charNotIn("\"").times.any();
		const quotePair = nonQuotes.and(exactly("\"")).times(2);
		const splitRegex = createRegExp(exactly(",").before(quotePair.times.any().and(nonQuotes).at.lineEnd()));
		for (const line of asnLines) {
			if (!line) continue;
			const parts = line.split(splitRegex);
			const match = parts[0].match(regex);
			const asnNumber = match?.groups.asnNumber ? parseInt(match.groups.asnNumber, 10) : null;
			const quoteStripRegex = createRegExp(exactly("\"").at.lineStart().or(exactly("\"").at.lineEnd()), ["g"]);
			const asnName = parts[1]?.replace(quoteStripRegex, "").trim() || "Unknown";
			const asnClass = parts[2]?.trim() || "Unknown";
			if (asnNumber !== null) asnDictionary.set(asnNumber, {
				name: asnName,
				type: asnClass
			});
		}
		logger$13.success(`SUCCESS: Loaded ${String(asnDictionary.size)} ASN definitions.`);
		logger$13.info("Fetching Routing Table...");
		const rawTable = await (await fetch(urls[1], {
			method: "GET",
			headers: { "User-Agent": userAgent }
		})).text();
		logger$13.info("Mapping CIDR to ASN data...");
		const routes = rawTable.split("\n");
		const results = [];
		for (const route of routes) {
			if (!route) continue;
			const parsed = JSON.parse(route);
			if (parsed.Hits < 10) continue;
			const dictionaryLookup = asnDictionary.get(parsed.ASN) ?? {
				name: "Unknown",
				type: "Unknown"
			};
			const record = {
				range: parsed.CIDR,
				asn_id: String(parsed.ASN),
				asn_name: dictionaryLookup.name,
				classification: dictionaryLookup.type,
				hits: String(parsed.Hits)
			};
			results.push(JSON.stringify(record));
		}
		if (results.length > 0) logger$13.log("SAMPLE:", results[0]);
		logger$13.info(`Writing ${String(results.length)} enriched entries to temporary JSON...`);
		fs$1.writeFileSync(tempASNJson, results.join("\n"), "utf-8");
		logger$13.info(`Compiling MMDB with mmdbctl to ${output}...`);
		const convert = await run(`${mmdbPath} import --in ${tempASNJson} --out ${output}`);
		if (convert.stdout) logger$13.log(`mmdbctl: ${convert.stdout.toString().trim()}`);
		logger$13.success(`COMPLETED: Successfully compiled ASN MMDB to ${output}\n`);
	} catch (error) {
		logger$13.error(`\nERROR during processing:`, error);
		process.exit(1);
	} finally {
		if (fs$1.existsSync(tempASNJson)) fs$1.unlinkSync(tempASNJson);
	}
}

//#endregion
//#region src/scripts/city.ts
const currentDir$1 = path$2.dirname(fileURLToPath(import.meta.url));
const dbPath$1 = [path$2.resolve(currentDir$1, "./countries+states+cities.json"), path$2.resolve(currentDir$1, "../../public/countries+states+cities.json")].find((p) => fs$1.existsSync(p)) ?? path$2.resolve(currentDir$1, "./countries+states+cities.json");
const logger$12 = consola$1.withTag("[CITY/GEO]");
/**
* Builds a city-level geolocation MMDB by fetching validated geofeed CSV data
* and enriching each entry with a local hierarchical country/state/city index.
* Output: `<outputPath>/city.mmdb`.
*
* @param outputPath - Directory where the compiled `city.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function buildCitiesData(outputPath, mmdbPath) {
	logger$12.info("\nBuilding global geographic index from hierarchical database...");
	const output = path$2.resolve(outputPath, "city.mmdb");
	const tempGeoJson = path$2.resolve(outputPath, "temp_city_data.json");
	const rawData = fs$1.readFileSync(dbPath$1, "utf8");
	const dbJson = JSON.parse(rawData);
	const countryIndex = /* @__PURE__ */ new Map();
	for (const country of dbJson) {
		const stateMap = /* @__PURE__ */ new Map();
		if (country.states) for (const state of country.states) {
			const cityMap = /* @__PURE__ */ new Map();
			if (state.cities) for (const city of state.cities) cityMap.set(city.name.toLowerCase(), city);
			stateMap.set(state.state_code, {
				meta: state,
				cities: cityMap
			});
		}
		countryIndex.set(country.iso2.toUpperCase(), {
			meta: country,
			states: stateMap
		});
	}
	try {
		logger$12.info("Fetching validated geofeed CSV...");
		const lines = (await (await fetch("https://geolocatemuch.com/geofeeds/validated-all.csv")).text()).split("\n");
		const commentRegex = createRegExp(exactly("#").at.lineStart());
		logger$12.success(`SUCCESS: Received ${String(lines.length)} IP ranges. Mapping to geographic index...`);
		const results = [];
		for (const line of lines) {
			if (!line.trim() || commentRegex.test(line)) continue;
			const parts = line.split(",");
			const prefix = parts[0]?.trim() || "";
			const country = parts[1]?.trim() || "";
			const region = parts[2]?.trim() || "";
			const city = parts[3]?.trim() || "";
			const zip = parts[4]?.trim() || "";
			if (!country && !city && !zip) continue;
			const cc = country.toUpperCase() || "";
			let emoji = "";
			let lat = "";
			let lon = "";
			let stateName = "";
			let countryMeta = {};
			const countryData = countryIndex.get(cc);
			if (countryData) {
				countryMeta = countryData.meta;
				emoji = countryData.meta.emoji || "";
				if (region) {
					const regionCode = region.includes("-") ? region.split("-").pop() : region;
					const stateData = countryData.states.get(regionCode);
					if (stateData) {
						stateName = stateData.meta.name || stateData.meta.state_code || "";
						if (city) {
							const cityData = stateData.cities.get(city.toLowerCase());
							if (cityData) {
								lat = cityData.latitude || "";
								lon = cityData.longitude || "";
							}
						}
						if (!lat && stateData.meta.latitude && stateData.meta.longitude) {
							lat = stateData.meta.latitude || "";
							lon = stateData.meta.longitude || "";
						}
					}
				}
				if (!lat && city) for (const [, stateData] of countryData.states) {
					const cityData = stateData.cities.get(city.toLowerCase());
					if (cityData) {
						lat = cityData.latitude || "";
						lon = cityData.longitude || "";
						if (!stateName) stateName = stateData.meta.name || stateData.meta.state_code || "";
						break;
					}
				}
				if (!lat && countryMeta.latitude && countryMeta.longitude) {
					lat = countryMeta.latitude || "";
					lon = countryMeta.longitude || "";
				}
			}
			const geoRecord = {
				range: prefix,
				country_code: country,
				region: region || (countryMeta.region ?? ""),
				subregion: countryMeta.subregion ?? "",
				city,
				zip_code: zip,
				latitude: lat,
				longitude: lon,
				state: stateName,
				name: countryMeta.name ?? "Unknown",
				native: countryMeta.native ?? "",
				phone: countryMeta.phonecode ?? "",
				numericCode: countryMeta.numeric_code ?? "",
				continent: countryMeta.region ?? "",
				capital: countryMeta.capital ?? "",
				currency: countryMeta.currency ?? "",
				currency_name: countryMeta.currency_name ?? "",
				iso639: countryMeta.iso639 ?? "",
				languages: countryMeta.translations ? Object.values(countryMeta.translations)[0] ?? "" : "",
				emoji,
				timezone: countryMeta.timezones?.[0]?.zoneName ?? "",
				utc_offset: countryMeta.timezones?.[0]?.gmtOffsetName ?? "",
				timeZoneName: countryMeta.timezones?.[0]?.tzName ?? "",
				tld: countryMeta.tld ?? "",
				nationality: countryMeta.nationality ?? ""
			};
			results.push(JSON.stringify(geoRecord));
		}
		logger$12.info(`Writing ${String(results.length)} enriched entries to temporary JSON...`);
		fs$1.writeFileSync(tempGeoJson, results.join("\n"), "utf-8");
		logger$12.start("Compiling MMDB with mmdbctl...");
		const convert = await run(`${mmdbPath} import --in ${tempGeoJson} --out ${output}`);
		if (convert.stdout) logger$12.log(`mmdbctl: ${convert.stdout.toString().trim()}`);
		logger$12.success(`COMPLETED: Successfully compiled City MMDB to ${output}\n`);
	} catch (error) {
		logger$12.error("\nERROR during processing:", error);
		process.exit(1);
	} finally {
		if (fs$1.existsSync(tempGeoJson)) fs$1.unlinkSync(tempGeoJson);
	}
}

//#endregion
//#region src/utils/generalCompiler/lmdbCompiler/convertor.ts
async function chunkProcess(items, chunkSize, processor) {
	if (chunkSize <= 0) throw new Error("chunkSize must be greater than 0");
	for (let i = 0; i < items.length; i += chunkSize) await processor(items.slice(i, i + chunkSize), i);
}
async function lmdbConvertor(outputPath, dbName, record, overides) {
	const db = open({
		path: outputPath,
		compression: true,
		pageSize: 4096,
		readOnly: false,
		name: dbName,
		useVersions: true,
		sharedStructuresKey: Symbol.for("structures"),
		cache: { validated: true },
		noReadAhead: true,
		maxReaders: 2024,
		...overides
	});
	if (!Array.isArray(record)) await db.put(record.key, record.data);
	else await chunkProcess(record, 1e4, async (chunk) => {
		const batch = chunk.map((item) => db.put(item.key, item.data));
		await Promise.all(batch);
	});
	await db.flushed;
	await db.close();
}

//#endregion
//#region src/scripts/disposableEmailList.ts
const logger$11 = consola.withTag("[Disposable-Email-List]");
/**
* Downloads the disposable email domain blocklist from disposable-email-domains
* and saves it as a plain-text file.
* Output: `<outputPath>/disposable_email_blocklist.txt`.
*
* @param outputPath - Directory where `disposable_email_blocklist.txt` will be written.
*/
async function getDisposableEmailList(outputPath) {
	const output = path$1.resolve(outputPath, "disposable_email_blocklist.txt");
	const url = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/refs/heads/main/disposable_email_blocklist.conf";
	logger$11.info("Fetching Disposable Email List...");
	try {
		const res = await fetch(url);
		if (!res.ok) {
			logger$11.error(`ERROR: Failed to fetch or body is empty: ${res.statusText}`);
			return;
		}
		await writeFile(output, await res.text(), "utf-8");
		logger$11.success("File downloaded successfully.");
		return;
	} catch (err) {
		logger$11.error("Failed downloading file", err);
		process.exit(1);
	}
}
/**
* Fetches the disposable email domain blocklist from disposable-email-domains
* and compiles it into an LMDB database for fast per-request domain lookups.
*
* Each domain is stored as-is (lowercased) as the key, enabling O(log n)
* exact lookups — ideal for checking whether an email domain is disposable
* on every incoming request.
*
* Output files:
* - `<outputPath>/email-db/disposable-emails.mdb` — the LMDB data file.
* - `<outputPath>/email-db/disposable-emails.mdb-lock` — LMDB lock file, generated automatically alongside the database.
*
* @param outputPath - Base directory where `email-db/disposable-emails.mdb` will be written.
*/
async function getDisposableEmailLmdbList(outputPath) {
	const output = path$1.resolve(process.cwd(), outputPath, "email-db", "disposable-emails.mdb");
	const url = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/refs/heads/main/disposable_email_blocklist.conf";
	const maintainer = "https://github.com/disposable-email-domains/disposable-email-domains";
	logger$11.info("Fetching Disposable Email List...");
	try {
		const res = await fetch(url);
		if (!res.ok) throw new Error(`ERROR: Failed to fetch country data: ${res.statusText}`);
		const lines = (await res.text()).split(/\r?\n/).map((l) => l.trim().toLowerCase()).filter(Boolean);
		logger$11.success(`SUCCESS: Received ${String(lines.length)} email domains. Cleaning data...`);
		const results = [];
		for (const domain of lines) {
			const record = {
				key: domain,
				data: {
					domain,
					date: (/* @__PURE__ */ new Date()).toISOString(),
					comment: `Maintained by ${maintainer} transformed by Shield-Base`
				}
			};
			results.push(record);
		}
		logger$11.info("Data sample:", results[0]);
		logger$11.success(`SUCCESS: Finished Cleaning. Discovered ${String(results.length)} unique records, Compiling...`);
		await lmdbConvertor(output, "email", results);
		logger$11.success(`COMPLETED: Successfully compiled disposable email data, into LMDB database, to ${output}\n`);
	} catch (err) {
		logger$11.error("\nERROR during processing:", err);
		process.exit(1);
	}
}

//#endregion
//#region src/scripts/tor.ts
const logger$10 = consola.withTag("[TOR]");
/**
* Fetches active Tor relay and exit node data from the Tor Project's Onionoo
* API and compiles it into an MMDB database.
* Output: `<outputPath>/tor.mmdb`.
*
* @param outputPath - Directory where the compiled `tor.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function getTorLists(outputPath, mmdbPath) {
	const url = "https://onionoo.torproject.org/details";
	const output = path.resolve(outputPath, "tor.mmdb");
	const tempFileName = path.resolve(outputPath, "temp_tor_nodes.json");
	try {
		logger$10.info("\nFetching node data from Onionoo API...");
		const response = await fetch(url, {
			method: "GET",
			headers: {
				"Accept-Encoding": "gzip",
				"Accept": "application/json"
			}
		});
		if (!response.ok) {
			logger$10.error(`ERROR: API fetch failed with status ${String(response.status)}`);
			return;
		}
		const data = await response.json();
		logger$10.success("SUCCESS: Data received, mapping relay nodes...");
		const toMap = (data.relays || []).filter((flags) => {
			const fl = flags.flags?.map((f) => f.toLowerCase()) ?? [];
			return fl.includes("exit") || fl.includes("valid") || fl.includes("running") || fl.includes("stable") || fl.includes("hsdir") && flags.exit_addresses && flags.or_addresses;
		});
		if (toMap.length === 0) {
			logger$10.warn("WARNING: No relay nodes found matching criteria.");
			return;
		}
		const results = [];
		for (const node of toMap) {
			const ipsToProcess = [...node.exit_addresses ?? [], ...node.or_addresses ?? []];
			if (!ipsToProcess) continue;
			for (const ip of ipsToProcess) {
				let cleanIp = ip;
				if (cleanIp.startsWith("[")) cleanIp = cleanIp.split("]:")[0].substring(1);
				else cleanIp = cleanIp.split(":")[0];
				const parsedIp = ipaddr.parse(cleanIp);
				let finalNetworkAddress;
				if (parsedIp.kind() === "ipv4") {
					const ipv4 = parsedIp;
					finalNetworkAddress = `${ipaddr.IPv4.networkAddressFromCIDR(`${ipv4.toString()}/24`).toString()}/24`;
				} else if (parsedIp.kind() === "ipv6") {
					const ipv6 = parsedIp;
					finalNetworkAddress = `${ipaddr.IPv6.networkAddressFromCIDR(`${ipv6.toString()}/64`).toString()}/64`;
				} else continue;
				results.push({
					range: finalNetworkAddress,
					or_addresses: node.or_addresses?.join(",") ?? "",
					exit_addresses: node.exit_addresses?.join(",") ?? "",
					last_seen: node.last_seen ?? "",
					last_changed_address_or_port: node.last_changed_address_or_port ?? "",
					first_seen: node.first_seen ?? "",
					running: node.running ?? false,
					flags: node.flags?.join(",") ?? "",
					country: node.country ?? "",
					country_name: node.country_name ?? "",
					as: node.as ?? "",
					as_name: node.as_name ?? "",
					last_restarted: node.last_restarted ?? "",
					exit_policy: node.exit_policy?.join(",") ?? "",
					exit_policy_summary: JSON.stringify(node.exit_policy_summary) || "",
					exit_policy_v6_summary: node.exit_policy_v6_summary ? JSON.stringify(node.exit_policy_v6_summary) : void 0,
					contact: node.contact ?? "Unknown",
					version_status: node.version_status ?? "",
					guard_probability: node.guard_probability ?? 0,
					middle_probability: node.middle_probability ?? 0,
					exit_probability: node.exit_probability ?? 0,
					recommended_version: node.recommended_version ?? false,
					measured: node.measured ?? false
				});
			}
		}
		logger$10.info("SAMPLE:", results[0]);
		const ndjsonOutput = results.map((record) => JSON.stringify(record)).join("\n");
		fs$1.writeFileSync(tempFileName, ndjsonOutput, "utf-8");
		try {
			logger$10.info("Compiling MMDB with mmdbctl...");
			const convert = await run(`${mmdbPath} import -j -i ${tempFileName} -o ${output}`);
			if (convert.stdout) logger$10.info(`mmdbctl: ${convert.stdout.toString().trim()}`);
			logger$10.success(`COMPLETED: Successfully compiled Tor MMDB to ${output}\n`);
		} catch (error) {
			logger$10.error("ERROR: MMDB compilation failed:", error);
			return;
		}
	} catch (error) {
		logger$10.error("ERROR: Unexpected failure while fetching node list:", error);
		return;
	} finally {
		if (fs$1.existsSync(tempFileName)) fs$1.unlinkSync(tempFileName);
	}
}

//#endregion
//#region src/scripts/geo.ts
const currentDir = path$2.dirname(fileURLToPath(import.meta.url));
const dbPath = [path$2.resolve(currentDir, "./countries+states+cities.json"), path$2.resolve(currentDir, "../../public/countries+states+cities.json")].find((p) => fs$1.existsSync(p)) ?? path$2.resolve(currentDir, "./countries+states+cities.json");
const logger$9 = consola.withTag("[GEO/COUNTRY]");
/**
* Fetches global IPv4-to-country mappings from Sapics and enriches each range
* with country metadata (name, currency, timezone, etc.) from a local database.
* Output: `<outputPath>/country.mmdb`.
*
* @param outputPath - Directory where the compiled `country.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function getGeoDatas(outputPath, mmdbPath) {
	logger$9.info("\nBuilding country index from local database...");
	const output = path$2.resolve(outputPath, "country.mmdb");
	const tempGeoJson = path$2.resolve(outputPath, "temp_country_data.json");
	const rawData = fs$1.readFileSync(dbPath, "utf8");
	const dbJson = JSON.parse(rawData);
	const countryIndex = /* @__PURE__ */ new Map();
	for (const country of dbJson) countryIndex.set(country.iso2.toUpperCase(), country);
	logger$9.info("Fetching global IPv4 Country mapping from Sapics...");
	const url = "https://raw.githubusercontent.com/sapics/ip-location-db/refs/heads/main/geo-asn-country/geo-asn-country-ipv4.csv";
	try {
		const res = await fetch(url);
		if (!res.ok) throw new Error(`ERROR: Failed to fetch country data: ${res.statusText}`);
		const lines = (await res.text()).split("\n");
		logger$9.success(`SUCCESS: Received ${String(lines.length)} global IP ranges. Enriching data...`);
		const results = [];
		for (const line of lines) {
			if (!line.trim()) continue;
			const parts = line.split(",");
			const start = parts[0]?.trim();
			const end = parts[1]?.trim();
			const cc = parts[2]?.trim().toUpperCase();
			if (!start || !end || !cc) continue;
			const countryMeta = countryIndex.get(cc);
			const record = {
				range: `${start}-${end}`,
				country_code: cc,
				region: countryMeta?.region ?? "",
				numericCode: countryMeta?.numeric_code ?? "",
				name: countryMeta?.name ?? "",
				native: countryMeta?.native ?? "",
				phone: countryMeta?.phonecode ?? "",
				capital: countryMeta?.capital ?? "",
				currency: countryMeta?.currency ?? "",
				currency_name: countryMeta?.currency_name ?? "",
				currency_symbol: countryMeta?.currency_symbol ?? "",
				iso639: countryMeta?.iso639 ?? "",
				languages: countryMeta?.translations ? Object.values(countryMeta.translations)[0] ?? "" : "",
				emoji: countryMeta?.emoji ?? "",
				timezone: countryMeta?.timezones?.[0]?.zoneName ?? "",
				utc_offset: countryMeta?.timezones?.[0]?.gmtOffsetName ?? "",
				timeZoneName: countryMeta?.timezones?.[0]?.tzName ?? "",
				tld: countryMeta?.tld ?? "",
				nationality: countryMeta?.nationality ?? "",
				subregion: countryMeta?.subregion ?? ""
			};
			results.push(JSON.stringify(record));
		}
		logger$9.info(`Writing ${String(results.length)} enriched entries to temporary JSON...`);
		fs$1.writeFileSync(tempGeoJson, results.join("\n"), "utf-8");
		logger$9.start("Compiling MMDB with mmdbctl...");
		const convert = await run(`${mmdbPath} import --in ${tempGeoJson} --out ${output}`);
		if (convert.stdout) logger$9.log(`mmdbctl: ${convert.stdout.toString().trim()}`);
		logger$9.success(`COMPLETED: Successfully compiled Country MMDB to ${output}\n`);
	} catch (error) {
		logger$9.error("\nERROR during processing:", error);
		process.exit(1);
	} finally {
		if (fs$1.existsSync(tempGeoJson)) fs$1.unlinkSync(tempGeoJson);
	}
}

//#endregion
//#region src/scripts/proxy.ts
const logger$8 = consola.withTag("[PROXY]");
/**
* Fetches and deduplicates proxy IP lists from mthcht/awesome-lists and the
* FireHOL proxy netset, then compiles them into an MMDB database.
* Output: `<outputPath>/proxy.mmdb`.
*
* @param outputPath - Directory where the compiled `proxy.mmdb` will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function getListOfProxies(outputPath, mmdbPath) {
	logger$8.log("\nFetching initial Proxy list from Awesome-lists...");
	const output = path$1.resolve(outputPath, "proxy.mmdb");
	const tempProxyJson = path$1.resolve(outputPath, "temp_proxy_data.json");
	const seenIps = /* @__PURE__ */ new Set();
	const results = [];
	const urls = ["https://raw.githubusercontent.com/mthcht/awesome-lists/refs/heads/main/Lists/PROXY/ALL_PROXY_Lists.csv", "https://raw.githubusercontent.com/firehol/blocklist-ipsets/refs/heads/master/firehol_proxies.netset"];
	const ipv4Regex = createRegExp(exactly(oneOrMore(digit), ".", oneOrMore(digit), ".", oneOrMore(digit), ".", oneOrMore(digit)).and(exactly("/", oneOrMore(digit)).optionally()).at.lineStart().at.lineEnd());
	try {
		const resAwesome = await fetch(urls[0]);
		if (!resAwesome.ok) throw new Error(`ERROR: Failed to fetch awesome list: ${resAwesome.statusText}`);
		const lines = (await resAwesome.text()).split("\n").slice(1);
		const nonQuotes = charNotIn("\"").times.any();
		const quotePair = nonQuotes.and(exactly("\"")).times(2);
		const splitAwesomeListRegex = createRegExp(exactly(",").before(quotePair.times.any().and(nonQuotes).at.lineEnd()));
		const stripAwesomeListRegex = createRegExp(anyOf(exactly("PROXY_ALL_"), exactly("_list.csv"), exactly("\"")), ["g", "i"]);
		for (const line of lines) {
			if (!line.trim()) continue;
			const [dest_ip, dest_port, metadata_comment] = line.split(splitAwesomeListRegex);
			if (!dest_ip || !ipv4Regex.test(dest_ip)) continue;
			if (seenIps.has(dest_ip)) continue;
			seenIps.add(dest_ip);
			const cleanComment = metadata_comment.replace(stripAwesomeListRegex, "").trim();
			const record = {
				range: `${dest_ip}/32`,
				port: dest_port,
				comment: cleanComment
			};
			results.push(JSON.stringify(record));
		}
		logger$8.success(`SUCCESS: Finished Awesome-list. Discovered ${String(results.length)} unique IPs.`);
		logger$8.log("Fetching secondary data from FireHOL proxy list...");
		const resFirehol = await fetch(urls[1]);
		if (!resFirehol.ok) throw new Error(`ERROR: Failed to fetch FireHOL list: ${resFirehol.statusText}`);
		const fireholLines = (await resFirehol.text()).split("\n");
		const commentRegex = createRegExp(exactly("#").at.lineStart());
		let fireholCount = 0;
		for (const line of fireholLines) {
			const trimmed = line.trim();
			if (!trimmed || commentRegex.test(trimmed)) continue;
			const ip = trimmed;
			if (!ipv4Regex.test(ip)) continue;
			if (seenIps.has(ip)) continue;
			seenIps.add(ip);
			const record = {
				range: ip,
				port: "unknown",
				comment: `Ip from Firehol`
			};
			results.push(JSON.stringify(record));
			fireholCount++;
		}
		logger$8.success(`SUCCESS: Finished FireHOL. Added ${String(fireholCount)} NEW unique proxies. Total unique: ${String(results.length)}`);
		if (results.length > 0) logger$8.log("SAMPLE:", results[0]);
		logger$8.log(`Writing ${String(results.length)} unique proxies to temporary JSON...`);
		fs.writeFileSync(tempProxyJson, results.join("\n"), "utf-8");
		logger$8.log("Compiling MMDB with mmdbctl...");
		const convert = await run(`${mmdbPath} import --in ${tempProxyJson} --out ${output}`);
		if (convert.stdout) logger$8.log(`mmdbctl: ${convert.stdout.toString().trim()}`);
		logger$8.success(`COMPLETED: Successfully compiled Proxy MMDB to ${output}\n`);
	} catch (error) {
		logger$8.error("\nERROR during processing:", error);
		process.exit(1);
	} finally {
		if (fs.existsSync(tempProxyJson)) fs.unlinkSync(tempProxyJson);
	}
}

//#endregion
//#region src/scripts/threats.ts
const THREAT_CONFIG = {
	firehol_anonymous: "https://github.com/firehol/blocklist-ipsets/raw/refs/heads/master/firehol_anonymous.netset",
	firehol_l1: "https://github.com/firehol/blocklist-ipsets/raw/refs/heads/master/firehol_level1.netset",
	firehol_l2: "https://github.com/firehol/blocklist-ipsets/raw/refs/heads/master/firehol_level2.netset",
	firehol_l3: "https://github.com/firehol/blocklist-ipsets/raw/refs/heads/master/firehol_level3.netset",
	firehol_l4: "https://github.com/firehol/blocklist-ipsets/raw/refs/heads/master/firehol_level4.netset"
};
const logger$7 = consola.withTag("[THREATS]");
/**
* Fetches one or more FireHOL blocklists and compiles each into a separate
* MMDB database named after its source (for example, `firehol_l1.mmdb`).
*
* @param outputPath - Directory where the compiled MMDB files will be written.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
* @param selectedSources - An array of FireHOL source IDs to compile
*   (`"firehol_l1"`, `"firehol_l2"`, `"firehol_l3"`, `"firehol_l4"`,
*   `"firehol_anonymous"`), or `true` to compile all of them.
*/
async function getThreatLists(outputPath, mmdbPath, selectedSources) {
	const maintainerUrl = " Maintainer: http://iplists.firehol.org/";
	const ipv4Regex = createRegExp(exactly(oneOrMore(digit), ".", oneOrMore(digit), ".", oneOrMore(digit), ".", oneOrMore(digit)).and(exactly("/", oneOrMore(digit)).optionally()).at.lineStart().at.lineEnd());
	const commentRegex = createRegExp(exactly("#").at.lineStart());
	let tasks = [];
	if (Array.isArray(selectedSources)) tasks = Object.entries(THREAT_CONFIG).filter(([id]) => selectedSources.includes(id)).map(([id, url]) => ({
		id,
		url
	}));
	else if (Boolean(selectedSources)) tasks = Object.entries(THREAT_CONFIG).map(([id, url]) => ({
		id,
		url
	}));
	if (tasks.length === 0) {
		logger$7.info("No valid FireHOL data sources selected. Skipping.");
		return;
	}
	try {
		logger$7.start(`Initializing fetch for ${String(tasks.length)} FireHOL stream(s)...`);
		const promises = tasks.map(async (task) => {
			const res = await fetch(task.url);
			return {
				id: task.id,
				res
			};
		});
		const resultsSettled = await Promise.allSettled(promises);
		logger$7.success(`SUCCESS: Fetched ${String(resultsSettled.length)} data streams from FireHOL.`);
		let ipsCount = 0;
		for (const result of resultsSettled) {
			if (result.status === "rejected") {
				logger$7.error(`Promise rejected:`, result.reason);
				continue;
			}
			const { id, res } = result.value;
			const finalMsg = `${id} ${maintainerUrl}`;
			const results = [];
			logger$7.info(`Processing stream: ${id}...`);
			if (!res.ok) {
				logger$7.error(`Failed fetching ${id}: ${res.statusText}`);
				continue;
			}
			const lines = (await res.text()).split("\n");
			for (const line of lines) {
				const trimmed = line.trim();
				if (!trimmed || commentRegex.test(trimmed)) continue;
				const ip = trimmed;
				if (!ipv4Regex.test(ip)) continue;
				const record = {
					range: ip,
					comment: finalMsg
				};
				ipsCount++;
				results.push(JSON.stringify(record));
			}
			logger$7.info(`${id} summary:\n
            → IPs Discovered: ${String(results.length)}\n
            → Rolling Total: ${String(ipsCount)}\n`);
			if (results.length > 0) {
				logger$7.info(`SAMPLE: ${results[0]}`);
				logger$7.info(`Writing entries to temporary JSON: temp_${id}.json`);
				const jsonName = path$1.resolve(outputPath, `temp_${id}.json`);
				const output = path$1.resolve(outputPath, `${id}.mmdb`);
				fs.writeFileSync(jsonName, results.join("\n"), "utf-8");
				console.log(`Compiling MMDB with mmdbctl to ${output}...`);
				const convert = await run(`${mmdbPath} import --in ${jsonName} --out ${output}`);
				if (convert.stdout) logger$7.box(`mmdbctl: ${convert.stdout.toString().trim()}`);
				if (fs.existsSync(jsonName)) fs.unlinkSync(jsonName);
				logger$7.success(`SUCCESS: Compiled ${id}.mmdb\n`);
			} else logger$7.warn(`WARNING: No valid IPs found for ${id}. Skipping.\n`);
		}
		logger$7.success("COMPLETED: All threat intelligence sources processed successfully.\n");
	} catch (error) {
		logger$7.error("\nFATAL ERROR during processing:", error);
		process.exit(1);
	}
}

//#endregion
//#region src/utils/fileDownloader.ts
const logger$6 = consola.withTag("[FILE-DOWNLOADER]");
/**
* Downloads a raw file from a URL and saves it to disk via a stream pipeline.
*
* @param outputPath - Absolute path where the file will be saved.
* @param url - URL of the file to download.
*/
async function downloadFile(outputPath, url) {
	try {
		const res = await fetch(url);
		if (!res.ok || !res.body) {
			logger$6.error(`ERROR: Failed to fetch or body is empty: ${res.statusText}`);
			return;
		}
		const fileStream = fs$1.createWriteStream(outputPath);
		await finished(Readable.fromWeb(res.body).pipe(fileStream));
		logger$6.success(`File downloaded and saved to ${outputPath}`);
		return;
	} catch (err) {
		logger$6.error("Error downloading file.", err);
		process.exit(1);
	}
}

//#endregion
//#region src/scripts/useragents.ts
const logger$5 = consola.withTag("[USERAGENT]");
/**
* Downloads the suspicious HTTP user-agent CSV list from mthcht/awesome-lists
* and saves it to disk.
* Output: `<outputPath>/useragent.csv`.
*
* @param outputPath - Directory where `useragent.csv` will be written.
*/
async function getUserAgentList(outputPath) {
	const output = path$2.resolve(outputPath, "useragent.csv");
	logger$5.start("Fetching useragent list...");
	const url = "https://raw.githubusercontent.com/mthcht/awesome-lists/refs/heads/main/Lists/suspicious_http_user_agents_list.csv";
	try {
		await downloadFile(output, url);
	} catch (err) {
		throw err;
	}
}
/**
* Fetches the suspicious HTTP user-agent CSV list from mthcht/awesome-lists,
* cleans each entry, and compiles it into an LMDB database for fast
* per-request lookups.
*
* Each user-agent pattern has its regex special characters escaped and
* wildcard `*` characters converted to `.*` so the stored value can be
* used directly as a regex pattern against incoming request user-agents.
* The raw user-agent string is used as the key.
*
* Output files:
* - `<outputPath>/useragent-db/useragent.mdb` — the LMDB data file.
* - `<outputPath>/useragent-db/useragent.mdb-lock` — LMDB lock file, generated automatically alongside the database.
*
* @param outputPath - Base directory where `useragent-db/useragent.mdb` will be written.
*/
async function getUserAgentLmdbList(outputPath) {
	const output = path$2.resolve(process.cwd(), outputPath, "useragent-db", "useragent.mdb");
	logger$5.start("Fetching useragent list...");
	const url = "https://raw.githubusercontent.com/mthcht/awesome-lists/refs/heads/main/Lists/suspicious_http_user_agents_list.csv";
	const maintainer = "https://github.com/mthcht/awesome-lists";
	try {
		const res = await fetch(url);
		if (!res.ok) throw new Error(`ERROR: Failed to fetch country data: ${res.statusText}`);
		const lines = (await res.text()).split("\n").slice(1);
		logger$5.success(`SUCCESS: Received ${String(lines.length)} user agents. Cleaning data...`);
		const results = [];
		const nonQuotes = charNotIn("\"").times.any();
		const quotePair = nonQuotes.and(exactly("\"")).times(2);
		const splitRegex = createRegExp(exactly(",").before(quotePair.times.any().and(nonQuotes).at.lineEnd()));
		const makeBool = (val) => val === "yes" ? true : val === "no" ? false : null;
		const cleanUa = (ua) => {
			if (!ua.includes("*")) return "^" + createRegExp(exactly(ua)).source + "$";
			return ua.split("*").map((s) => createRegExp(exactly(s)).source).join(".*");
		};
		for (const line of lines) {
			if (!line.trim()) continue;
			const [http_user_agent, metadata_description, metadata_tool, metadata_category, metadata_link, metadata_priority, metadata_fp_risk, metadata_severity, metadata_usage, metadata_flow_from_external, metadata_flow_from_internal, metadata_flow_to_internal, metadata_flow_to_external, metadata_for_successful_external_login_events, metadata_comment] = line.split(splitRegex);
			if (!http_user_agent) continue;
			const record = {
				key: http_user_agent,
				data: {
					useragent_rx: cleanUa(http_user_agent),
					metadata_description,
					metadata_tool,
					metadata_category,
					metadata_link,
					metadata_priority,
					metadata_fp_risk,
					metadata_severity,
					metadata_usage,
					metadata_flow_from_external: makeBool(metadata_flow_from_external),
					metadata_flow_from_internal: makeBool(metadata_flow_from_internal),
					metadata_flow_to_internal: makeBool(metadata_flow_to_internal),
					metadata_flow_to_external: makeBool(metadata_flow_to_external),
					metadata_for_successful_external_login_events: makeBool(metadata_for_successful_external_login_events),
					metadata_comment: metadata_comment ?? "N/A",
					date: (/* @__PURE__ */ new Date()).toISOString(),
					comment: `Data maintained by ${maintainer}, transformed by Shield-base`
				}
			};
			results.push(record);
		}
		logger$5.info("Data sample:", results[0]);
		logger$5.success(`SUCCESS: Finished Cleaning. Discovered ${String(results.length)} unique records, Compiling...`);
		await lmdbConvertor(output, "useragent", results);
		logger$5.success(`COMPLETED: Successfully compiled Useragents data, into LMDB database, to ${output}\n`);
	} catch (err) {
		logger$5.error("\nERROR during processing:", err);
		process.exit(1);
	}
}

//#endregion
//#region src/scripts/ja4.ts
const logger$4 = consola.withTag("[JA4]");
/**
* Downloads the JA4+ Database json fingerprints from https://ja4db.com
* and saves it to disk.
* Output: `<outputPath>/ja4.json`.
*
* @param outputPath - Directory where `ja4.json` will be written.
*/
async function getJaDatabase(outputPath) {
	const output = path$2.resolve(outputPath, "ja4.json");
	logger$4.start("Fetching JA4+ Database...");
	const url = "https://ja4db.com/api/read/";
	try {
		await downloadFile(output, url);
	} catch (err) {
		throw err;
	}
}
/**
* Streams the JA4+ Database directly from https://ja4db.com and compiles it
* into an LMDB database for fast per-request fingerprint lookups.
*
* Each record is exploded by its non-null fingerprint fields so that any
* JA4 variant (`ja4`, `ja4s`, `ja4h`, `ja4x`, `ja4t`, `ja4ts`, `ja4tscan`)
* can be looked up directly by its fingerprint string as the key.
*
* Output files:
* - `<outputPath>/ja4-db/ja4.mdb` — the LMDB data file.
* - `<outputPath>/ja4-db/ja4.mdb-lock` — LMDB lock file, generated automatically alongside the database.
*
* @param outputPath - Base directory where `ja4-db/ja4.mdb` will be written.
*/
async function getJaDatabaseLmdb(outputPath) {
	const output = path$2.resolve(process.cwd(), outputPath, "ja4-db", "ja4.mdb");
	const url = "https://ja4db.com/api/read/";
	const maintainer = `https://ja4db.com`;
	logger$4.start("Fetching JA4+ Database...");
	const FP_FIELDS = [
		"ja4_fingerprint",
		"ja4s_fingerprint",
		"ja4h_fingerprint",
		"ja4x_fingerprint",
		"ja4t_fingerprint",
		"ja4ts_fingerprint",
		"ja4tscan_fingerprint"
	];
	try {
		const res = await fetch(url);
		if (!res.ok) throw new Error(`Failed to fetch JA4 data: ${res.statusText}`);
		const records = [];
		const pipeline = chain([
			Readable$1.fromWeb(res.body),
			parser(),
			streamArray()
		]);
		for await (const { value } of pipeline) {
			const entry = value;
			for (const field of FP_FIELDS) {
				const fp = entry[field];
				if (fp) records.push({
					key: fp,
					data: {
						...entry,
						date: (/* @__PURE__ */ new Date()).toISOString(),
						comment: `Maintained by ${maintainer}, transformed by Shield-Base`
					}
				});
			}
		}
		logger$4.success(`SUCCESS: Discovered ${String(records.length)} fingerprint entries. Compiling...`);
		logger$4.info("Data sample:", records[0]);
		await lmdbConvertor(output, "ja4", records);
		logger$4.success(`COMPLETED: Successfully compiled JA4 data into LMDB database, to ${output}`);
	} catch (err) {
		logger$4.error("\nERROR during processing:", err);
		process.exit(1);
	}
}

//#endregion
//#region src/scripts/executeAll.ts
const logger$3 = consola.withTag("Shield Base");
/**
* Runs the full Shield-Base data pipeline in parallel, compiling all available
* sources: BGP/ASN, city, Tor, country, proxies, threat lists, verified
* crawlers, suspicious user agents, disposable email domains and ja4 fingerprints.
*
* @param outputPath - Directory where all compiled databases will be written.
* @param userAgent - BGP.tools contact string (format: `<name> [url] - <email>`).
* @param selectedSources - FireHOL source IDs to include, or `true` for all FireHOL lists.
* @param mmdbPath - Path to the `mmdbctl` binary, or `"mmdbctl"` if it is on PATH.
*/
async function generateData(outputPath, userAgent, selectedSources, mmdbPath) {
	logger$3.box("\n=========================================\n === Starting data generation pipeline === \n=========================================\n");
	try {
		(await Promise.allSettled([
			getBGPAndASN(userAgent, outputPath, mmdbPath),
			buildCitiesData(outputPath, mmdbPath),
			getTorLists(outputPath, mmdbPath),
			getGeoDatas(outputPath, mmdbPath),
			getListOfProxies(outputPath, mmdbPath),
			getThreatLists(outputPath, mmdbPath, selectedSources),
			getCrawlersIps(outputPath, mmdbPath),
			getUserAgentLmdbList(outputPath),
			getDisposableEmailLmdbList(outputPath),
			getJaDatabaseLmdb(outputPath)
		])).forEach((result, index) => {
			if (result.status === "rejected") logger$3.error(`Script cluster [${String(index)}] failed:`, String(result.reason));
		});
		logger$3.info("\n====================================\nPipeline execution finished.\n====================================\n");
	} catch (error) {
		logger$3.error(`
====================================
Fatal error in pipeline: ${String(error)}
====================================
`);
	}
}

//#endregion
//#region src/utils/restart.ts
const cacheOutput = path$1.join(os.homedir(), ".shield-base", ".cache.json");
/**
* Re-runs data compilation using settings saved in the Shield-Base cache
* (`~/.shield-base/.cache.json`). Requires at least one prior successful run.
*
* @param outputPath - Directory where recompiled databases will be written.
* @param all - When `true`, recompiles all available data sources. When `false`,
*   recompiles only the sources recorded in the cache.
*/
async function restartData(outputPath, all) {
	if (!existsSync(cacheOutput)) {
		consola.error("No cache found. Cannot restart. Please run a standard compilation first.");
		process.exit(1);
	}
	const cachedFile = await readFile(cacheOutput, "utf-8");
	const cache = JSON.parse(cachedFile);
	if (!cache.selectedDataTypes || cache.selectedDataTypes.length === 0) {
		consola.warn("Cache is empty. No data sources to restart.");
		return;
	}
	if (all) {
		consola.info("Restarting ALL data sources...");
		await generateData(outputPath, cache.useragent, true, cache.mmdbctlPath);
		consola.success("✨ All data successfully refreshed!");
		return;
	}
	consola.info(`Restarting data compilation from cache: ${cache.selectedDataTypes.join(", ")}`);
	const fireholSources = cache.selectedDataTypes.filter((s) => s.startsWith("firehol_") || s === "anonymous");
	const standardSources = cache.selectedDataTypes.filter((s) => !s.startsWith("firehol_") && s !== "anonymous");
	const executionRestartQueue = [];
	if (standardSources.includes("BGP")) executionRestartQueue.push({
		name: "BGP & ASN",
		task: () => getBGPAndASN(cache.useragent || "", outputPath, cache.mmdbctlPath)
	});
	if (standardSources.includes("City")) executionRestartQueue.push({
		name: "City",
		task: () => buildCitiesData(cache.outPutPath, cache.mmdbctlPath)
	});
	if (standardSources.includes("Geography")) executionRestartQueue.push({
		name: "Geography",
		task: () => getGeoDatas(cache.outPutPath, cache.mmdbctlPath)
	});
	if (standardSources.includes("Email")) executionRestartQueue.push({
		name: "Email",
		task: () => getDisposableEmailList(cache.outPutPath)
	});
	if (standardSources.includes("UserAgent")) executionRestartQueue.push({
		name: "UserAgent",
		task: () => getUserAgentList(cache.outPutPath)
	});
	if (standardSources.includes("JA4")) executionRestartQueue.push({
		name: "JA4",
		task: () => getJaDatabase(cache.outPutPath)
	});
	if (standardSources.includes("SEO")) executionRestartQueue.push({
		name: "SEO",
		task: () => getCrawlersIps(cache.outPutPath, cache.mmdbctlPath)
	});
	if (standardSources.includes("Proxy")) executionRestartQueue.push({
		name: "Proxy",
		task: () => getListOfProxies(cache.outPutPath, cache.mmdbctlPath)
	});
	if (standardSources.includes("Tor")) executionRestartQueue.push({
		name: "Tor",
		task: () => getTorLists(cache.outPutPath, cache.mmdbctlPath)
	});
	if (fireholSources.length > 0) executionRestartQueue.push({
		name: "Threats",
		task: () => getThreatLists(cache.outPutPath, cache.mmdbctlPath, fireholSources)
	});
	consola.start(`Running ${String(executionRestartQueue.length)} restart jobs...`);
	(await Promise.allSettled(executionRestartQueue.map((q) => q.task()))).forEach((res, index) => {
		if (res.status === "rejected") consola.error(`[${executionRestartQueue[index].name}] Failed:`, res.reason);
	});
	consola.success("✨ Cached data successfully refreshed!");
}

//#endregion
//#region src/utils/generalCompiler/normalizers.ts
function normalizePaths(data, type) {
	if (typeof data === "string") return path.resolve(process.cwd(), data);
	if (type === "lmdb") return {
		output: path.resolve(process.cwd(), data.outputPath, `${data.dataBaseName}.mdb`),
		pathToJson: path.resolve(process.cwd(), data.pathToJson),
		dataBaseName: data.dataBaseName
	};
	else return {
		output: path.resolve(process.cwd(), data.outputPath, `${data.dataBaseName}.mmdb`),
		pathToJson: path.resolve(process.cwd(), data.pathToJson),
		tempJsonOutput: path.resolve(process.cwd(), data.outputPath, `temp_json_${data.dataBaseName}.json`),
		dataBaseName: data.dataBaseName
	};
}
function resolveInputData(type, data) {
	if (typeof data === "string") return {
		resolved: path.resolve(process.cwd(), data),
		isPathArray: false
	};
	const isStringOfSourcesArray = (arr) => {
		return arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null && "pathToJson" in arr[0] && "dataBaseName" in arr[0];
	};
	if (Array.isArray(data) && isStringOfSourcesArray(data)) if (type === "lmdb") return {
		resolved: data.map((s) => normalizePaths(s, "lmdb")),
		isPathArray: true
	};
	else return {
		resolved: data.map((s) => normalizePaths(s, "mmdb")),
		isPathArray: true
	};
	if (type === "lmdb") return {
		resolved: data,
		isPathArray: false
	};
	return {
		resolved: data,
		isPathArray: false
	};
}

//#endregion
//#region src/utils/generalCompiler/generateTypes.ts
const logger$2 = consola.withTag("TYPE-GEN");
/**
* Generates TypeScript type definitions from a JSON source and writes them to
* a `.ts` file. The source can be an in-memory object/array, a raw JSON
* string, or a path to a `.json` file.
*
* @param jsonSource - The JSON data to derive types from.
* @param name - Base name for the root interface and output file
*   (for example, `"MyDb"` → `mydbTypes.ts`).
* @param outPutPath - Directory where the generated type file will be written.
*/
function generateTypeFile(jsonSource, name, outPutPath) {
	let parsedJson;
	if (typeof jsonSource === "object" && jsonSource !== null) {
		logger$2.info(`Processing direct JSON object for ${name}...`);
		parsedJson = jsonSource;
	} else if (typeof jsonSource === "string") try {
		parsedJson = JSON.parse(jsonSource);
		logger$2.info(`Processing JSON string for ${name}...`);
	} catch {
		try {
			const fileContent = fs.readFileSync(jsonSource, "utf-8");
			logger$2.info(`Reading JSON from file: ${jsonSource}`);
			parsedJson = JSON.parse(fileContent);
		} catch {
			throw new Error(`Failed to parse as JSON, and failed to read/parse as file path: ${jsonSource}`);
		}
	}
	else throw new Error("jsonSource must be a JSON object, a JSON string, or a valid file path.");
	const pathDir = normalizePaths(outPutPath);
	const fileContent = JsonToTS(parsedJson, {
		rootName: name,
		useTypeAlias: false
	}).join("\n\n");
	const fileName = `${name.toLowerCase()}Types.ts`;
	const fullPath = path$1.join(pathDir, fileName);
	fs.writeFileSync(fullPath, fileContent, "utf-8");
	logger$2.success(`Type definitions for ${name} written to ${fullPath}`);
}

//#endregion
//#region src/utils/generalCompiler/mmdbCompiler/convertor.ts
async function convertor(output, mmdbPath, tempJsonPath, results) {
	try {
		fs.writeFileSync(tempJsonPath, results.join("\n"), "utf-8");
		const convert = await run(`${mmdbPath} import --in ${tempJsonPath} --out ${output}`);
		if (convert.stdout) consola.log(`mmdbctl: ${convert.stdout.toString().trim()}`);
	} catch (err) {
		throw err;
	}
}

//#endregion
//#region src/utils/generalCompiler/mmdbCompiler/generalCompiler.ts
const logger$1 = consola.withTag("COMPILER");
/**
* Compiles JSON data into one or more MMDB databases via `mmdbctl`. Accepts
* three input forms: a raw object array in memory, a path to a `.json` file,
* or an array of {@link StringOfSources} for batch processing.
*
* Every record in the data must contain a `range` field with an IPv4/IPv6
* address or CIDR range.
*
* @param input - Compilation config: data, output path, database name,
*   mmdbctl path, and whether to generate TypeScript types.
*/
async function mmdbCompiler(input) {
	if (!fs.existsSync(normalizePaths(input.outputPath))) {
		logger$1.info(`Creating output directory: ${input.outputPath}`);
		fs.mkdirSync(normalizePaths(input.outputPath), { recursive: true });
	}
	const { resolved: INPUT_DATA, isPathArray } = resolveInputData("mmdb", input.data);
	if (isPathArray) {
		for (const dataSource of INPUT_DATA) if (!fs.existsSync(dataSource.pathToJson)) {
			logger$1.fatal(`Couldn't find ${dataSource.pathToJson} does it exists?`);
			process.exit(1);
		}
	} else if (typeof INPUT_DATA === "string") {
		if (!fs.existsSync(INPUT_DATA)) {
			logger$1.fatal(`Couldn't find ${INPUT_DATA} does it exists?`);
			process.exit(1);
		}
	}
	if (typeof INPUT_DATA === "string" || isPathArray) {
		logger$1.info("Processing data from file(s)...");
		const arraysOfData = Array.isArray(INPUT_DATA) ? INPUT_DATA : [INPUT_DATA];
		for (const dataToProcess of arraysOfData) {
			const paths = typeof dataToProcess === "string" ? dataToProcess : dataToProcess.pathToJson;
			const data = fs.readFileSync(paths, "utf-8");
			const dbJson = JSON.parse(data);
			if (!Array.isArray(dbJson)) {
				logger$1.fatal("Data source must be an array of records.");
				process.exit(1);
			}
			const results = [];
			for (const record of dbJson) {
				if (!("range" in record)) {
					logger$1.fatal("Each record in the array must contain a 'range' field.");
					process.exit(1);
				}
				const recordsToInsert = { ...record };
				results.push(JSON.stringify(recordsToInsert));
			}
			logger$1.info(`Compiling ${results.length.toString()} records into ${input.dataBaseName}.mmdb...`);
			const tempJson = typeof dataToProcess === "string" ? path$1.resolve(normalizePaths(input.outputPath), `temp_json_${input.dataBaseName}.json`) : dataToProcess.tempJsonOutput;
			const output = typeof dataToProcess === "string" ? path$1.resolve(normalizePaths(input.outputPath), `${input.dataBaseName}.mmdb`) : dataToProcess.output;
			const name = typeof dataToProcess === "string" ? input.dataBaseName : dataToProcess.dataBaseName;
			try {
				await convertor(output, input.mmdbPath, tempJson, results);
				if (input.generateTypes) {
					logger$1.info(`Generating TypeScript definitions for ${name}...`);
					generateTypeFile(dbJson, name, input.outputPath);
					logger$1.success(`Types generated successfully for ${name}`);
				}
			} catch (err) {
				logger$1.error(`Failed to process ${name}:`, err);
				throw err;
			} finally {
				if (fs.existsSync(tempJson)) fs.unlinkSync(tempJson);
			}
		}
	} else {
		const output = path$1.resolve(normalizePaths(input.outputPath), `${input.dataBaseName}.mmdb`);
		const tempJson = path$1.resolve(normalizePaths(input.outputPath), `temp_json_${input.dataBaseName}.json`);
		const dbJson = INPUT_DATA;
		if (!Array.isArray(dbJson)) {
			logger$1.fatal("Data source must be an array of records.");
			process.exit(1);
		}
		const results = [];
		for (const record of dbJson) {
			if (!("range" in record)) {
				logger$1.fatal("Each record in the array must contain a 'range' field.");
				process.exit(1);
			}
			const recordsToInsert = { ...record };
			results.push(JSON.stringify(recordsToInsert));
		}
		try {
			logger$1.info(`Processing raw records for ${input.dataBaseName}...`);
			await convertor(output, input.mmdbPath, tempJson, results);
			if (input.generateTypes) {
				logger$1.info(`Generating TypeScript definitions for ${input.dataBaseName}...`);
				generateTypeFile(dbJson, input.dataBaseName, input.outputPath);
				logger$1.success(`Types generated successfully for ${input.dataBaseName}`);
			}
			logger$1.success(`Compilation complete for ${input.dataBaseName}`);
			return;
		} catch (err) {
			logger$1.error(`Failed to process ${input.dataBaseName}:`, err);
			throw err;
		} finally {
			if (fs.existsSync(tempJson)) fs.unlinkSync(tempJson);
		}
	}
}

//#endregion
//#region src/utils/generalCompiler/lmdbCompiler/generalCompiler.ts
const logger = consola.withTag("LMDB-COMPILER");
/**
* Compiles data into an LMDB database. Accepts three input forms: a raw
* object array in memory, a path to a `.json` file, or an array of
* {@link StringOfSources} for batch processing.
*
* Each record must contain a `key` or `id` field this value becomes the
* LMDB lookup key. Records missing both fields cause a fatal error.
* Invalid or non-object entries are skipped with a warning.
*
* When `generateTypes` is `true`, a TypeScript type definition file is
* written alongside the database using the first record's shape.
*
* Output files:
* - `<outputPath>/<dataBaseName>.mdb` - the LMDB data file.
* - `<outputPath>/<dataBaseName>.mdb-lock` - LMDB lock file, generated automatically alongside the database.
*
* @param input - Compilation config: data, output path, database name, and if to generate types.
*/
async function lmdbCompiler(input) {
	if (!fs.existsSync(normalizePaths(input.outputPath))) {
		logger.info(`Creating output directory: ${input.outputPath}`);
		fs.mkdirSync(normalizePaths(input.outputPath), { recursive: true });
	}
	const { resolved: INPUT_DATA, isPathArray } = resolveInputData("lmdb", input.data);
	if (isPathArray) {
		for (const dataSource of INPUT_DATA) if (!fs.existsSync(dataSource.pathToJson)) {
			logger.fatal(`Couldn't find ${dataSource.pathToJson} does it exists?`);
			process.exit(1);
		}
	} else if (typeof INPUT_DATA === "string") {
		if (!fs.existsSync(INPUT_DATA)) {
			logger.fatal(`Couldn't find ${INPUT_DATA} does it exists?`);
			process.exit(1);
		}
	}
	if (typeof INPUT_DATA === "string" || isPathArray) {
		logger.info("Processing data from file(s)...");
		const arraysOfData = Array.isArray(INPUT_DATA) ? INPUT_DATA : [INPUT_DATA];
		for (const dataToProcess of arraysOfData) {
			const filePath = typeof dataToProcess === "string" ? dataToProcess : dataToProcess.pathToJson;
			const data = fs.readFileSync(filePath, "utf-8");
			const dbJson = JSON.parse(data);
			const results = [];
			if (!Array.isArray(dbJson)) {
				logger.fatal("Data source must be an array of records.");
				process.exit(1);
			}
			for (const record of dbJson) {
				if (!record || typeof record !== "object") {
					logger.warn(`Skipping invalid or empty record entry: 
                        ${String(record)}
                    `);
					continue;
				}
				const item = record;
				if (!("key" in item) && !("id" in item)) {
					logger.fatal("Each record must contain at least an 'id' or a 'key' field.");
					process.exit(1);
				}
				const identifier = item.key ?? item.id;
				const { key, id, ...cleanData } = item;
				const recordToInsert = {
					key: identifier,
					data: cleanData
				};
				results.push(recordToInsert);
			}
			logger.info(`Compiling ${results.length.toString()} records into ${input.dataBaseName}.mdb...`);
			const output = typeof dataToProcess === "string" ? path$1.resolve(normalizePaths(input.outputPath), `${input.dataBaseName}.mdb`) : dataToProcess.output;
			const name = typeof dataToProcess === "string" ? input.dataBaseName : dataToProcess.dataBaseName;
			try {
				await lmdbConvertor(output, name, results);
				if (input.generateTypes) {
					logger.info(`Generating TypeScript definitions for ${name}...`);
					generateTypeFile(dbJson, name, input.outputPath);
					logger.success(`Types generated successfully for ${name}`);
				}
			} catch (err) {
				logger.error(`Failed to process ${name}:`, err);
				throw err;
			}
		}
	} else {
		const output = path$1.resolve(normalizePaths(input.outputPath), `${input.dataBaseName}.mdb`);
		const dbJson = INPUT_DATA;
		if (!Array.isArray(dbJson)) {
			logger.fatal("Data source must be an array of records.");
			process.exit(1);
		}
		const results = [];
		for (const record of dbJson) {
			if (!record || typeof record !== "object") {
				logger.warn(`Skipping invalid or empty record entry: ${String(record)}`);
				continue;
			}
			const item = record;
			if (!("key" in item) && !("id" in item)) {
				logger.fatal("Each record must contain at least an 'id' or a 'key' field.");
				process.exit(1);
			}
			const identifier = item.key ?? item.id;
			const { key, id, ...cleanData } = item;
			const recordToInsert = {
				key: identifier,
				data: cleanData
			};
			results.push(recordToInsert);
		}
		logger.info(`Compiling ${results.length.toString()} records into ${input.dataBaseName}.mdb...`);
		try {
			await lmdbConvertor(output, input.dataBaseName, results);
			logger.success(`Compilation complete for ${input.dataBaseName}`);
			if (input.generateTypes) {
				logger.info(`Generating TypeScript definitions for ${input.dataBaseName}...`);
				generateTypeFile(dbJson, input.dataBaseName, input.outputPath);
				logger.success(`Types generated successfully for ${input.dataBaseName}`);
			}
		} catch (err) {
			logger.error(`Failed to process ${input.dataBaseName}:`, err);
			throw err;
		}
	}
}

//#endregion
//#region src/utils/generalCompiler/compiler.ts
async function compiler(options) {
	if (options.type === "lmdb") {
		await lmdbCompiler(options.input);
		return;
	} else {
		await mmdbCompiler(options.input);
		return;
	}
}

//#endregion
//#region src/utils/generalCompiler/lmdbCompiler/reader.ts
function openDb(dbPath, dbName, overrides) {
	return open({
		path: dbPath,
		compression: true,
		readOnly: true,
		name: dbName,
		useVersions: true,
		sharedStructuresKey: Symbol.for("structures"),
		...overrides
	});
}
/** Get a single record by exact key */
function getByKey(dbPath, dbName, key) {
	const db = openDb(dbPath, dbName);
	const result = db.get(key);
	consola.log(`[get] key="${key}"`, result ?? "(not found)");
	db.close();
	if (result) return result;
}
/** Get the first N records */
function getRange(dbPath, dbName, limit = 10) {
	const db = openDb(dbPath, dbName);
	const results = [];
	for (const { key, value } of db.getRange({ limit })) results.push({
		key,
		data: value
	});
	consola.log(`[range] first ${limit.toString()} records:`);
	console.dir(results, { depth: 5 });
	db.close();
	return results;
}
/** Get records whose key starts with a prefix */
function getByPrefix(dbPath, dbName, prefix, limit = 20) {
	const db = openDb(dbPath, dbName);
	const results = [];
	for (const { key, value } of db.getRange({
		start: prefix,
		limit
	})) {
		if (!key.startsWith(prefix)) break;
		results.push({
			key,
			data: value
		});
	}
	consola.log(`[prefix] key prefix="${prefix}" 
     (${results.length.toString()} results):`);
	console.dir(results, { depth: 5 });
	db.close();
	return results;
}
/** Count total records */
function countRecords(dbPath, dbName) {
	const db = openDb(dbPath, dbName);
	const count = db.getCount();
	consola.log(`[count] total records: ${count.toString()}`);
	db.close();
	return count;
}
/** Check whether a key exists without reading the value */
function doesExist(dbPath, dbName, key) {
	const db = openDb(dbPath, dbName);
	const exists = db.doesExist(key);
	consola.log(`[exists] key="${key}":`, exists);
	db.close();
	return exists;
}
/** Return low level LMDB environment statistics (page size, depth, record count, etc.) */
function stats(dbPath, dbName) {
	const db = openDb(dbPath, dbName);
	const stats = db.getStats();
	consola.log(stats);
	db.close();
	return stats;
}
/**
* Permanently delete all records and the named sub database.
* This operation is irreversible. The database file remains on disk but the
* sub-database is wiped. Opens the environment in write mode.
*/
async function drop(dbPath, dbName) {
	const db = openDb(dbPath, dbName, { readOnly: false });
	await db.drop();
	consola.log(`Deleted`);
	db.close();
}

//#endregion
export { ipBase as A, buildCitiesData as C, normalizeExtractedMatch as D, run as E, normalizeIp as O, getDisposableEmailLmdbList as S, getCrawlersIps as T, getThreatLists as _, getByPrefix as a, getTorLists as b, compiler as c, generateData as d, getJaDatabase as f, downloadFile as g, getUserAgentLmdbList as h, getByKey as i, botIpExtractor as k, generateTypeFile as l, getUserAgentList as m, doesExist as n, getRange as o, getJaDatabaseLmdb as p, drop as r, stats as s, countRecords as t, restartData as u, getListOfProxies as v, getBGPAndASN as w, getDisposableEmailList as x, getGeoDatas as y };
//# sourceMappingURL=scripts-DRNH_1sI.mjs.map